﻿using Linguist.Acoustic;
using Linguist.Language.Grammar;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Util;
using Util.Props;

namespace Linguist.Flat
{
    /// <summary>
    /// 
    /// A simple form of the linguist.
    ///
    /// The flat linguist takes a Grammar graph (as returned by the underlying, configurable grammar), and generates a search
    /// graph for this grammar.
    ///
    /// It makes the following simplifying assumptions:
    ///
    /// Zero or one word per grammar node <li> No fan-in allowed ever <li> No composites (yet) <li> Only Unit,
    /// HMMState, and pronunciation states (and the initial/final grammar state are in the graph (no word, alternative or
    /// grammar states attached). <li> Only valid transitions (matching contexts) are allowed <li> No tree organization of
    /// units <li> Branching grammar states are  allowed </ul>
    ///
    /// Note that all probabilities are maintained in the log math domain
    /// </summary>
    public partial class FlatLinguist: Linguist,IConfigurable
    {
         /**
        /// The property used to define the grammar to use when building the search graph
         */
        [S4Component(type = typeof(Grammar))]
        public static String PROP_GRAMMAR = "grammar";

        /**
        /// The property used to define the unit manager to use when building the search graph
         */
        [S4Component(type = typeof(UnitManager))]
        public static String PROP_UNIT_MANAGER = "unitManager";

        /**
        /// The property used to define the acoustic model to use when building the search graph
         */
        [S4Component(type = typeof(IAcousticModel))]
        public static String PROP_ACOUSTIC_MODEL = "acousticModel";
    
        /**
        /// The property used to determine whether or not the gstates are dumped.
         */
        [S4Boolean(defaultValue = false)]
        public static String PROP_DUMP_GSTATES = "dumpGstates";

        /**
        /// The property that specifies whether to add a branch for detecting out-of-grammar utterances.
         */
        [S4Boolean(defaultValue = false)]
        public static String PROP_ADD_OUT_OF_GRAMMAR_BRANCH = "addOutOfGrammarBranch";

        /**
        /// The property for the probability of entering the out-of-grammar branch.
         */
        [S4Double(defaultValue = 1.0)]
        public static String PROP_OUT_OF_GRAMMAR_PROBABILITY = "outOfGrammarProbability";

        /**
        /// The property for the acoustic model used for the CI phone loop.
         */
        [S4Component(type = typeof(IAcousticModel))]
        public static String PROP_PHONE_LOOP_ACOUSTIC_MODEL = "phoneLoopAcousticModel";

        /**
        /// The property for the probability of inserting a CI phone in the out-of-grammar ci phone loop
         */
        [S4Double(defaultValue = 1.0)]
        public static String PROP_PHONE_INSERTION_PROBABILITY = "phoneInsertionProbability";

        /**
        /// Property to control whether compilation progress is displayed on standard output. 
        /// If this property is true, a 'dot' is  displayed for every 1000 search states added
        ///  to the search space
         */
        [S4Boolean(defaultValue = false)]
        public static String PROP_SHOW_COMPILATION_PROGRESS = "showCompilationProgress";

        /**
        /// Property that controls whether word probabilities are spread across all pronunciations.
         */
        [S4Boolean(defaultValue = false)]
        public static String PROP_SPREAD_WORD_PROBABILITIES_ACROSS_PRONUNCIATIONS =
                "spreadWordProbabilitiesAcrossPronunciations";

        protected static float logOne = LogMath.LOG_ONE;

        // note: some fields are protected to allow to override FlatLinguist.compileGrammar()

        // ----------------------------------
        // Subcomponents that are configured
        // by the property sheet
        // -----------------------------------
        protected Grammar grammar;
        private IAcousticModel acousticModel;
        public UnitManager unitManager;
        protected LogMath logMath;

        // ------------------------------------
        // Fields that define the OOV-behavior
        // ------------------------------------
        protected IAcousticModel phoneLoopAcousticModel;
        protected Boolean addOutOfGrammarBranch;
        protected float logOutOfGrammarBranchProbability;
        protected float logPhoneInsertionProbability;

        // ------------------------------------
        // Data that is configured by the
        // property sheet
        // ------------------------------------
        private float logWordInsertionProbability;
        private float logSilenceInsertionProbability;
        private float logFillerInsertionProbability;
        private float logUnitInsertionProbability;
        private Boolean showCompilationProgress = true;
        private Boolean spreadWordProbabilitiesAcrossPronunciations;
        private Boolean dumpGStates;
        private float languageWeight;

        // -----------------------------------
        // Data for monitoring performance
        // ------------------------------------
        protected StatisticsVariable totalStates;
        protected StatisticsVariable totalArcs;
        protected StatisticsVariable actualArcs;
        private int totalStateCounter;
        private static Boolean tracing = false;

        // ------------------------------------
        // Data used for building and maintaining
        // the search graph
        // -------------------------------------
        private List<SentenceHMMState> stateSet;
        private String name;
        public Dictionary<GrammarNode, GState> nodeStateMap =  new Dictionary<GrammarNode,GState>();
        protected Cache<SentenceHMMStateArc> arcPool;
        protected GrammarNode initialGrammarState;

        protected ISearchGraph searchGraph;


        /**
        /// Returns the search graph
         *
        /// @return the search graph
         */
        override public ISearchGraph getSearchGraph() 
        {
            return searchGraph;
        }

        public FlatLinguist(IAcousticModel acousticModel, Grammar grammar, UnitManager unitManager,
                double wordInsertionProbability, double silenceInsertionProbability, double fillerInsertionProbability,
                double unitInsertionProbability, float languageWeight, Boolean dumpGStates, Boolean showCompilationProgress,
                Boolean spreadWordProbabilitiesAcrossPronunciations, Boolean addOutOfGrammarBranch,
                double outOfGrammarBranchProbability, double phoneInsertionProbability, IAcousticModel phoneLoopAcousticModel) 
        {

            this.acousticModel = acousticModel;
            this.logMath = LogMath.getLogMath();
            this.grammar = grammar;
            this.unitManager = unitManager;

            this.logWordInsertionProbability = logMath.linearToLog(wordInsertionProbability);
            this.logSilenceInsertionProbability = logMath.linearToLog(silenceInsertionProbability);
            this.logFillerInsertionProbability = logMath.linearToLog(fillerInsertionProbability);
            this.logUnitInsertionProbability = logMath.linearToLog(unitInsertionProbability);
            this.languageWeight = languageWeight;
        
            this.dumpGStates = dumpGStates;
            this.showCompilationProgress = showCompilationProgress;
            this.spreadWordProbabilitiesAcrossPronunciations = spreadWordProbabilitiesAcrossPronunciations;

            this.addOutOfGrammarBranch = addOutOfGrammarBranch;

            if (addOutOfGrammarBranch) {
                this.logOutOfGrammarBranchProbability = logMath.linearToLog(outOfGrammarBranchProbability);
                this.logPhoneInsertionProbability = logMath.linearToLog(phoneInsertionProbability);
                this.phoneLoopAcousticModel = phoneLoopAcousticModel;
            }

            this.name = null;
        }

        public FlatLinguist() {

        }

        /*
       /// (non-Javadoc)
        *
       /// @see edu.cmu.sphinx.util.props.Configurable#newProperties(edu.cmu.sphinx.util.props.PropertySheet)
        */
        override public void newProperties(PropertySheet ps)
        {
            logMath = LogMath.getLogMath();

            acousticModel = (IAcousticModel) ps.getComponent(PROP_ACOUSTIC_MODEL);
            grammar = (Grammar) ps.getComponent(PROP_GRAMMAR);
            unitManager = (UnitManager) ps.getComponent(PROP_UNIT_MANAGER);

            // get the rest of the configuration data
            logWordInsertionProbability = logMath.linearToLog(ps.getDouble(PROP_WORD_INSERTION_PROBABILITY));
            logSilenceInsertionProbability = logMath.linearToLog(ps.getDouble(PROP_SILENCE_INSERTION_PROBABILITY));
            logFillerInsertionProbability = logMath.linearToLog(ps.getDouble(PROP_FILLER_INSERTION_PROBABILITY));
            logUnitInsertionProbability = logMath.linearToLog(ps.getDouble(PROP_UNIT_INSERTION_PROBABILITY));
            languageWeight = ps.getFloat(Linguist.PROP_LANGUAGE_WEIGHT);
            dumpGStates = ps.getBoolean(PROP_DUMP_GSTATES);
            showCompilationProgress = ps.getBoolean(PROP_SHOW_COMPILATION_PROGRESS);
            spreadWordProbabilitiesAcrossPronunciations = ps.getBoolean(PROP_SPREAD_WORD_PROBABILITIES_ACROSS_PRONUNCIATIONS);

            addOutOfGrammarBranch = ps.getBoolean(PROP_ADD_OUT_OF_GRAMMAR_BRANCH);

            if (addOutOfGrammarBranch) {
                logOutOfGrammarBranchProbability = logMath.linearToLog(ps.getDouble(PROP_OUT_OF_GRAMMAR_PROBABILITY));
                logPhoneInsertionProbability = logMath.linearToLog(ps.getDouble(PROP_PHONE_INSERTION_PROBABILITY));
                phoneLoopAcousticModel = (IAcousticModel)ps.getComponent(PROP_PHONE_LOOP_ACOUSTIC_MODEL);
            }

            name = ps.InstanceName;
        }


        /*
       /// (non-Javadoc)
        *
       /// @see edu.cmu.sphinx.util.props.Configurable#getName()
        */
        public String getName() 
        {
            return name;
        }


        /*
       /// (non-Javadoc)
        *
       /// @see edu.cmu.sphinx.linguist.Linguist#allocate()
        */
        override public void allocate()
        {
            allocateAcousticModel();
            grammar.allocate();
            totalStates = StatisticsVariable.getStatisticsVariable(getName(), "totalStates");
            totalArcs = StatisticsVariable.getStatisticsVariable(getName(), "totalArcs");
            actualArcs = StatisticsVariable.getStatisticsVariable(getName(), "actualArcs");
            Trace.WriteLine("Grammar compilation not implemented!");
            stateSet = compileGrammar();
            totalStates.value = stateSet.Count;
        }


        /**
        /// Allocates the acoustic model.
        /// @throws java.io.IOException
         */
        protected void allocateAcousticModel()
        {
            acousticModel.allocate();
            if (addOutOfGrammarBranch) {
                phoneLoopAcousticModel.allocate();
            }
        }


        /*
       /// (non-Javadoc)
        *
       /// @see edu.cmu.sphinx.linguist.Linguist#deallocate()
        */
        override public void deallocate() 
        {
            if (acousticModel != null) {
                acousticModel.deallocate();
            }
            grammar.deallocate();
        }


        /**
        /// Called before a recognition
         */
        override public void startRecognition() 
        {
            if (grammarHasChanged())
            {
                stateSet = compileGrammar();
                totalStates.value = stateSet.Count;
            }
        }


        /**
        /// Called after a recognition
         */
        override public void stopRecognition() 
        {
        }

        /**
        /// Returns the log silence insertion probability.
         *
        /// @return the log silence insertion probability.
         */
        public float getLogSilenceInsertionProbability() 
        {
            return logSilenceInsertionProbability;
        }

        
        /// <summary>
        /// Compiles the grammar into a sentence HMM. A GrammarJob is created for the
        /// initial grammar node and added to the GrammarJob queue. While there are
        /// jobs left on the grammar job queue, a job is removed from the queue and
        /// the associated grammar node is expanded and attached to the tails.
        /// GrammarJobs for the successors are added to the grammar job queue.
        /// </summary>
        /// <returns></returns>
        protected List<SentenceHMMState> compileGrammar() 
        {
            initialGrammarState = grammar.getInitialNode();

            ///nodeStateMap = new Dictionary<GrammarNode, GState>();
            /// create in declaration section (22.12.2014)

            arcPool = new Cache<SentenceHMMStateArc>();

            List<GState> gstateList = new List<GState>();
            TimerPool.getTimer(this, "Compile").start();

            // get the nodes from the grammar and create states
            // for them. Add the non-empty gstates to the gstate list.
            TimerPool.getTimer(this, "Create States").start();
            foreach(GrammarNode grammarNode in grammar.getGrammarNodes()) 
            {
                GState gstate = createGState(grammarNode);
                gstateList.Add(gstate);
            }
            TimerPool.getTimer(this, "Create States").stop();
            addStartingPath();

            // ensures an initial path to the start state
            // Prep all the gstates, by gathering all of the contexts up
            // this allows each gstate to know about its surrounding contexts
            TimerPool.getTimer(this, "Collect Contexts").start();
            foreach (GState gstate in gstateList)
                gstate.collectContexts();
            TimerPool.getTimer(this, "Collect Contexts").stop();

            // now all gstates know all about their contexts, we can expand them fully
            TimerPool.getTimer(this, "Expand States").start();
            foreach (GState gstate in gstateList)
                gstate.expand();
            TimerPool.getTimer(this, "Expand States").stop();

            // now that all states are expanded fully, we can connect all the states up
            TimerPool.getTimer(this, "Connect Nodes").start();
            foreach (GState gstate in gstateList)
                gstate.connect();
            TimerPool.getTimer(this, "Connect Nodes").stop();

            SentenceHMMState initialState = findStartingState();

            // add an out-of-grammar branch if configured to do so
            if (addOutOfGrammarBranch) 
            {
                CIPhoneLoop phoneLoop = new CIPhoneLoop(phoneLoopAcousticModel, logPhoneInsertionProbability);
                SentenceHMMState firstBranchState = (SentenceHMMState)
                        phoneLoop.getSearchGraph().getInitialState();
                initialState.connect(getArc(firstBranchState, logOne, logOutOfGrammarBranchProbability));
            }

            searchGraph = new FlatSearchGraph(initialState);
            TimerPool.getTimer(this, "Compile").stop();
            // Now that we are all done, dump out some interesting
            // information about the process
            if (dumpGStates) 
            {
                foreach (GrammarNode grammarNode in grammar.getGrammarNodes()) 
                {
                    GState gstate = getGState(grammarNode);
                    gstate.dumpInfo();
                }
            }
            nodeStateMap = null;
            arcPool = null;
            return SentenceHMMState.collectStates(initialState);
        }


        /// <summary>
        /// Returns a new GState for the given GrammarNode.
        /// </summary>
        /// <param name="grammarNode"></param>
        /// <returns>a new GState for the given GrammarNode</returns>
        protected GState createGState(GrammarNode grammarNode) 
        {
            return new GState(grammarNode, this);
        }


        /// <summary>
        /// Ensures that there is a starting path by adding an empty left context to the starting gstate
        /// </summary>
        protected void addStartingPath() 
        {
            // TODO: Currently the FlatLinguist requires that the initial
            // grammar node returned by the Grammar contains a "sil" word
            addStartingPath(grammar.getInitialNode());
        }

        /// <summary>
        /// Start the search at the indicated node
        /// </summary>
        /// <param name="initialNode"></param>
        protected void addStartingPath(GrammarNode initialNode) 
        {
            // guarantees a starting path into the initial node by
            // adding an empty left context to the starting gstate
            GState gstate = getGState(initialNode);
            gstate.addLeftContext(UnitContext.SILENCE);
        }


        /**
        /// Determines if the underlying grammar has changed since we last compiled the search graph
         *
        /// @return true if the grammar has changed
         */
        protected Boolean grammarHasChanged() 
        {
            return initialGrammarState == null ||
                    initialGrammarState != grammar.getInitialNode();
        }


        /// <summary>
        /// Finds the starting state
        /// </summary>
        /// <returns></returns>
        protected SentenceHMMState findStartingState() 
        {
            GrammarNode node = grammar.getInitialNode();
            GState gstate = getGState(node);
            return gstate.getEntryPoint();
        }


        /**
        /// Gets a SentenceHMMStateArc. The arc is drawn from a pool of arcs.
         *
        /// @param nextState               the next state
        /// @param logLanguageProbability  the log language probability
        /// @param logInsertionProbability the log insertion probability
         */
        protected SentenceHMMStateArc getArc(SentenceHMMState nextState,
                                             float logLanguageProbability,
                                             float logInsertionProbability) 
        {
            SentenceHMMStateArc arc = new SentenceHMMStateArc(nextState,
                    logLanguageProbability* languageWeight,
                    logInsertionProbability);
            SentenceHMMStateArc pooledArc = arcPool.cache(arc);
            actualArcs.value = arcPool.getMisses();
            totalArcs.value = arcPool.getHits() + arcPool.getMisses();
            return pooledArc == null ? arc : pooledArc;
        }


        /**
        /// Given a grammar node, retrieve the grammar state
         *
        /// @param node the grammar node
        /// @return the grammar state associated with the node
         */
        protected GState getGState(GrammarNode node) 
        {
            return nodeStateMap[node];
        }

        /**
        /// Quick and dirty tracing. Traces the string if 'tracing' is true
         *
        /// @param s the string to trace.
         */
        private void T(String s) 
        {
            if (tracing) {
                Trace.WriteLine(s);
            }
        }

        void IConfigurable.newProperties(PropertySheet ps)
        {
            newProperties(ps);
        }
    }

  
    
    
    

    
}
